package main

import (
	"bytes"
	"compress/gzip"
	"context"
	"encoding/base64"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"slices"
	"strings"
)

type EnvName string
type IgnorePath string

var (
	cache           ICache
	envIgnoreProxy  map[EnvName]map[IgnorePath]*httputil.ReverseProxy
	envDefaultProxy map[EnvName]*httputil.ReverseProxy
	ctx             = context.Background()
	keyBase         = "proxyTrek"
	proxyHost       = "localhost:7001" // gateway中trek监听的地址
	targetEnv       = EnvName("trek.test-tx-8.e7link.com")
	config          = IConfig{
		Host: []string{
			"trek.test-tx-8.e7link.com",
			"localhost:7000",
		},
		Path: []string{
			"/appPassport/refreshToken",
			"/appPassport/getRefreshTokenByIpChange",
			"/web/asyncTask/status",
			"/web/entity/asyncBatchDo/BudgetPlan",
		},
		Query: []string{
			"fetchId",
		},
		IgnoreHost: "trek.test-tx-8.e7link.com",
		IgnorePath: []string{
			"/appPassport/refreshToken",
			"/appPassport/getRefreshTokenByIpChange",
		},
		IgnoreQuery: []string{
			"fetchId",
		},
	}
	env = map[EnvName]IEnvConfig{
		"localhost:7000": {
			DefaultHost: "localhost:7000",
			IgnorePath: map[string]IgnorePathConfig{
				"/appPassport/refreshToken": {
					Switch: 1,
				},
				"/appPassport/getRefreshTokenByIpChange": {
					Switch: 1,
				},
				"/web/asyncTask/status": {
					Switch: 1,
				},
				"/web/entity/asyncBatchDo/BudgetPlan": {
					Switch: 1,
				},
			},
		},
	}
)

func main() {
	cache = NewMemoryCache()
	makeEnv(targetEnv)
	envIgnoreProxy, envDefaultProxy = makeProxy()

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		var proxy = envIgnoreProxy[targetEnv][IgnorePath(r.URL.Path)]
		if env[targetEnv].IgnorePath[r.URL.Path].Switch == 1 {
			fmt.Println("DIRECT PROXY:", r.URL.String())
			proxy.Director = func(req *http.Request) {
				host := env[targetEnv].IgnorePath[r.URL.Path].Host
				if host == "" {
					host = env[targetEnv].DefaultHost
				}
				req.Header = r.Header
				req.Host = host
				req.URL.Scheme = "http"
				req.URL.Host = host
				req.URL.Path = r.URL.Path
				req.URL.RawQuery = r.URL.RawQuery
			}
			proxy.ServeHTTP(w, r)
		} else {
			proxyHandler(w, r, envDefaultProxy[targetEnv], env[targetEnv].DefaultHost)
		}
	})

	go func() {
		// 代理后台
		log.Fatal(http.ListenAndServe(proxyHost, nil))
	}()
	// 管理页面
	view()
}

func makeEnv(envName EnvName) {
	env[envName] = IEnvConfig{
		DefaultHost: string(envName),
		IgnorePath:  make(map[string]IgnorePathConfig),
	}
	for _, v := range config.Path {
		env[envName].IgnorePath[v] = IgnorePathConfig{
			Switch: 0,
		}
	}
	for _, v := range config.IgnorePath {
		env[envName].IgnorePath[v] = IgnorePathConfig{
			Switch: 1,
		}
	}

}

func makeProxy() (map[EnvName]map[IgnorePath]*httputil.ReverseProxy, map[EnvName]*httputil.ReverseProxy) {
	envIgnoreProxy = make(map[EnvName]map[IgnorePath]*httputil.ReverseProxy)
	envDefaultProxy = make(map[EnvName]*httputil.ReverseProxy)
	for envName, envConfig := range env {
		ignorePathProxy := make(map[IgnorePath]*httputil.ReverseProxy)
		for path, pathConfig := range envConfig.IgnorePath {
			if pathConfig.Host == "" {
				pathConfig.Host = envConfig.DefaultHost
			}
			ignorePathProxy[IgnorePath(path)] = httputil.NewSingleHostReverseProxy(&url.URL{
				Scheme: "http",
				Host:   pathConfig.Host,
			})
		}
		envIgnoreProxy[envName] = ignorePathProxy
		envDefaultProxy[envName] = httputil.NewSingleHostReverseProxy(&url.URL{
			Scheme: "http",
			Host:   envConfig.DefaultHost,
		})
	}
	return envIgnoreProxy, envDefaultProxy
}

func proxyHandler(w http.ResponseWriter, r *http.Request, proxy *httputil.ReverseProxy, targetHost string) {
	buf4cache, reqBody := handleRequestBody(r)
	defer r.Body.Close()
	newRawURL := handleIgnoreQuery(r, config.IgnoreQuery)
	tenantId := r.Header.Get("Tenant-Id")
	cacheKey := makeCacheKey(tenantId, newRawURL, buf4cache)
	cachedData, _ := cache.Get(cacheKey)
	if cachedData != "" {
		fmt.Println("find   cache:", r.URL.String())
		fmt.Fprint(w, cachedData)
		return
	}

	fmt.Println("NO     cache:", r.URL.String())
	proxyReq, _ := http.NewRequest(r.Method, newRawURL, reqBody)
	proxyReq.Header = r.Header
	proxyReq.Host = targetHost
	rww := ResponseWriterWrapper{w: w}
	proxy.ServeHTTP(&rww, proxyReq)
	body_ := handleGzip(rww)
	resBody := string(body_)
	resBody = strings.TrimSpace(resBody)

	handleCache(rww, cacheKey, resBody, r)
}

func handleRequestBody(r *http.Request) (*bytes.Buffer, *bytes.Buffer) {
	buf4cache := bytes.NewBuffer(nil)
	reqBody := bytes.NewBuffer(nil)

	body := io.TeeReader(r.Body, io.MultiWriter(buf4cache, reqBody)) // 将请求的Body同时写入buf和reqBody
	_, _ = io.ReadAll(body)                                          // 不知道在干嘛，但删了就不对了
	return buf4cache, reqBody
}

// makeCacheKey 根据 header body url 生成 key
func makeCacheKey(tenantId, url string, buf4cache *bytes.Buffer) string {
	var body = buf4cache.String()
	cacheKey := strings.Join([]string{url, tenantId, body}, "@")
	cacheKey = base64.StdEncoding.EncodeToString([]byte(cacheKey))
	cacheKey = strings.Join([]string{keyBase, cacheKey}, "@@")
	return cacheKey
}

func handleCache(rww ResponseWriterWrapper, cacheKey string, resBody string, r *http.Request) {
	if rww.statusCode == 200 {
		if len(resBody) > 0 {
			cache.Set(cacheKey, resBody)
			fmt.Println("SET    cache:", r.URL.String(), len(resBody))
		} else {
			cache.Set(cacheKey, "")
			fmt.Println("EMPTY BODY:", r.URL.String(), len(resBody))
		}
	} else if rww.statusCode == 401 {
		fmt.Println("REMOVE cache:", r.URL.String(), len(resBody))
		cache.Del(cacheKey)
	} else {
		fmt.Println("request error 1.1:", rww.statusCode, r.URL.String())
		fmt.Println("request error 1.2:", resBody)
	}
}

func handleIgnoreQuery(r *http.Request, ignoreQuery []string) string {
	q := r.URL.Query()
	q1 := make(url.Values)
	for k, v := range q {
		if slices.Contains(ignoreQuery, k) {
			fmt.Println("ignore query:", k)
			continue
		}
		q1.Add(k, v[0])
	}
	r.URL.RawQuery = q1.Encode()
	newRawURL := r.URL.String()
	return newRawURL
}

func handleGzip(rww ResponseWriterWrapper) (body_ []byte) {
	ungzipbody, err := gzip.NewReader(&rww.body)
	if err != nil {
		fmt.Println(110.1, err)
		body_ = rww.body.Bytes()
	} else {
		body_, err = io.ReadAll(ungzipbody)
	}
	if err != nil {
		fmt.Println(110.2, err)
	}
	return
}

type IgnorePathConfig struct {
	Host   string
	Switch int
}
type IEnvConfig struct {
	IgnorePath   map[string]IgnorePathConfig
	DefaultHost  string
	DefaultProxy *httputil.ReverseProxy
}
